package org.jjche.file.modules.file.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.hash.HashInfo;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.jjche.common.enums.FileType;
import org.jjche.common.param.MyPage;
import org.jjche.common.param.PageParam;
import org.jjche.core.util.FileUtil;
import org.jjche.file.modules.file.api.vo.FileVO;
import org.jjche.file.modules.file.domain.FileDetailDO;
import org.jjche.file.modules.file.dto.FileQueryCriteriaDTO;
import org.jjche.file.modules.file.mapper.FileMapper;
import org.jjche.file.modules.file.mapstruct.FileDetailMapStruct;
import org.jjche.mybatis.base.service.MyServiceImpl;
import org.jjche.mybatis.param.SortEnum;
import org.jjche.mybatis.util.MybatisUtil;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 文件服务
 * </p>
 *
 * @author miaoyj
 * @since 2024-07-10
 */
@Service
@RequiredArgsConstructor
public class FileService extends MyServiceImpl<FileMapper, FileDetailDO> implements FileRecorder {

    private final FileDetailMapStruct fileMapStruct;
    private final FilePartDetailService filePartDetailService;

    /**
     * <p>
     * 获取列表查询语句
     * </p>
     *
     * @param criteria 条件
     * @return sql
     */
    private LambdaQueryWrapper queryWrapper(FileQueryCriteriaDTO criteria) {
        LambdaQueryWrapper queryWrapper = MybatisUtil.assemblyLambdaQueryWrapper(criteria, SortEnum.ID_DESC);
        String blurry = criteria.getBlurry();
        if (StrUtil.isNotBlank(blurry)) {
            queryWrapper.apply("(name LIKE {0} OR suffix LIKE {0} OR type LIKE {0})", "%" + blurry + "%");
        }
        return queryWrapper;
    }

    /**
     * 分页查询
     *
     * @param criteria 条件
     * @param pageable 分页参数
     * @return /
     */
    public MyPage<FileVO> pageQuery(FileQueryCriteriaDTO criteria, PageParam pageable) {
        LambdaQueryWrapper queryWrapper = queryWrapper(criteria);
        MyPage myPage = this.page(pageable, queryWrapper);
        List<FileVO> list = fileMapStruct.toVO(myPage.getRecords());
        myPage.setNewRecords(list);
        return myPage;
    }

    /**
     * 查询全部数据
     *
     * @param criteria 条件
     * @return /
     */
    public List<FileVO> queryAll(FileQueryCriteriaDTO criteria) {
        LambdaQueryWrapper queryWrapper = queryWrapper(criteria);
        return fileMapStruct.toVO(this.list(queryWrapper));
    }

    /**
     * 多选删除
     *
     * @param ids /
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteByIds(List<String> ids) {
        this.removeByIds(ids);
    }

    /**
     * 多选删除
     *
     * @param urls /
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByUrls(Set<String> urls) {
        return remove(new LambdaQueryWrapper<FileDetailDO>()
                .in(FileDetailDO::getUrl, urls)
                .or()
                .in(FileDetailDO::getThUrl, urls));
    }

    /**
     * <p>
     * 根据id获取url
     * </p>
     *
     * @param ids
     * @return /
     */
    public Set<String> listUrlByIds(List<String> ids) {
        Set<String> filePaths = CollUtil.newHashSet();
        List<FileDetailDO> files = this.listByIds(ids);
        if (CollUtil.isNotEmpty(files)) {
            filePaths = files.stream().map(FileDetailDO::getUrl).collect(Collectors.toSet());
        }
        return filePaths;
    }

    /**
     * 导出数据
     *
     * @param queryAll 待导出的数据
     * @param response /
     * @throws IOException if any.
     */
    public void download(List<FileVO> queryAll, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (FileVO fileDTO : queryAll) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("文件名", fileDTO.getOriginalFilename());
            map.put("文件类型", fileDTO.getType().getText());
            map.put("文件大小", fileDTO.getSizeStr());
            map.put("创建者", fileDTO.getCreatedBy());
            map.put("创建日期", fileDTO.getGmtCreate());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    /**
     * 保存文件信息到数据库
     */
    @SneakyThrows
    @Override
    public boolean save(FileInfo info) {
        FileDetailDO detail = toFileDetail(info);
        FileType type = FileUtil.getFileType(detail.getExt());
        detail.setType(type);
        boolean b = save(detail);
        if (b) {
            info.setId(String.valueOf(detail.getId()));
        }
        return b;
    }

    /**
     * 更新文件记录，可以根据文件 ID 或 URL 来更新文件记录，
     * 主要用在手动分片上传文件-完成上传，作用是更新文件信息
     */
    @SneakyThrows
    @Override
    public void update(FileInfo info) {
        FileDetailDO detail = toFileDetail(info);
        LambdaQueryWrapper<FileDetailDO> qw = Wrappers.lambdaQuery();
        qw.eq(detail.getUrl() != null, FileDetailDO::getUrl, detail.getUrl());
        qw.eq(detail.getId() != null, FileDetailDO::getId, detail.getId());
        update(detail, qw);
    }

    /**
     * 根据 url 查询文件信息
     */
    @SneakyThrows
    @Override
    public FileInfo getByUrl(String url) {
        return toFileInfo(getOne(new LambdaQueryWrapper<FileDetailDO>()
                .eq(FileDetailDO::getUrl, url)
                .or()
                .in(FileDetailDO::getThUrl, url)
        ));
    }

    /**
     * 根据 url 删除文件信息
     */
    @Override
    public boolean delete(String url) {
        return remove(new LambdaQueryWrapper<FileDetailDO>()
                .eq(FileDetailDO::getUrl, url)
                .or()
                .eq(FileDetailDO::getThUrl, url));
    }

    /**
     * 保存文件分片信息
     *
     * @param filePartInfo 文件分片信息
     */
    @Override
    public void saveFilePart(FilePartInfo filePartInfo) {
        filePartDetailService.saveFilePart(filePartInfo);
    }

    /**
     * 删除文件分片信息
     */
    @Override
    public void deleteFilePartByUploadId(String uploadId) {
        filePartDetailService.deleteFilePartByUploadId(uploadId);
    }

    /**
     * 将 FileInfo 转为 FileDetail
     */
    public FileDetailDO toFileDetail(FileInfo info) throws JsonProcessingException {
        FileDetailDO detail = BeanUtil.copyProperties(
                info, FileDetailDO.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo");
        // 这里手动获 元数据 并转成 json 字符串，方便存储在数据库中
        detail.setMetadata(valueToJson(info.getMetadata()));
        detail.setUserMetadata(valueToJson(info.getUserMetadata()));
        detail.setThMetadata(valueToJson(info.getThMetadata()));
        detail.setThUserMetadata(valueToJson(info.getThUserMetadata()));
        // 这里手动获 取附加属性字典 并转成 json 字符串，方便存储在数据库中
        detail.setAttr(valueToJson(info.getAttr()));
        // 这里手动获 哈希信息 并转成 json 字符串，方便存储在数据库中
        detail.setHashInfo(valueToJson(info.getHashInfo()));
        return detail;
    }

    /**
     * 将 FileDetailDO 转为 FileInfo
     */
    public FileInfo toFileInfo(FileDetailDO detail) throws JsonProcessingException {
        FileInfo info = BeanUtil.copyProperties(
                detail, FileInfo.class, "metadata", "userMetadata", "thMetadata", "thUserMetadata", "attr", "hashInfo");
        if (info != null) {
            // 这里手动获取数据库中的 json 字符串 并转成 元数据，方便使用
            info.setMetadata(jsonToMetadata(detail.getMetadata()));
            info.setUserMetadata(jsonToMetadata(detail.getUserMetadata()));
            info.setThMetadata(jsonToMetadata(detail.getThMetadata()));
            info.setThUserMetadata(jsonToMetadata(detail.getThUserMetadata()));
            // 这里手动获取数据库中的 json 字符串 并转成 附加属性字典，方便使用
            info.setAttr(jsonToDict(detail.getAttr()));
            // 这里手动获取数据库中的 json 字符串 并转成 哈希信息，方便使用
            info.setHashInfo(jsonToHashInfo(detail.getHashInfo()));
        }
        return info;
    }

    /**
     * 将指定值转换成 json 字符串
     */
    public String valueToJson(Object value) throws JsonProcessingException {
        return JSONUtil.toJsonStr(value);
    }

    /**
     * 将 json 字符串转换成元数据对象
     */
    public Map<String, String> jsonToMetadata(String json) throws JsonProcessingException {
        return JSONUtil.toBean(json, Map.class);
    }

    /**
     * 将 json 字符串转换成字典对象
     */
    public Dict jsonToDict(String json) throws JsonProcessingException {
        return JSONUtil.toBean(json, Dict.class);
    }

    /**
     * 将 json 字符串转换成哈希信息对象
     */
    public HashInfo jsonToHashInfo(String json) throws JsonProcessingException {
        return JSONUtil.toBean(json, HashInfo.class);
    }
}
